#include <errno.h>  //ENOENT
//#include <error.h>
#include <fcntl.h>
#include <myFs/FSbasic.h>
#include <myFs/blockbuf.h>
#include <myFs/fscomponent.h>
#include <myFs/fsinterface.h>
#include <spdlog/spdlog.h>
//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
//#include <algorithm>
#include <cmath>
#include <cstdlib>  //exit
//#include <fstream>
//#include <functional>
//#include <map>
//#include <memory>
#include <sstream>
//#include <stdexcept>
#include <string>
#include <vector>
//#include "fsinterface.h"
//#include "fscache.h"

namespace myFs {
namespace basic {
using namespace spdlog;
using size_t = uint32_t;
FSbasic::~FSbasic() {
    spdlog::info("FSbasic deconstruct");
    munmap(m_baseptr, m_size);
    close(m_fd);
};
FSbasic::FSbasic(::std::string filename) {
    spdlog::info("FSbasic construct");
    mapFileIntoMemory(filename);
    m_meta = *((Meta*)(m_baseptr));

    if (!m_meta.isFormated()) {
        spdlog::info("clear and format myFS dumpfile");
        memset(m_baseptr, 0, m_size);
        spdlog::info("build default meta");
        m_meta = Meta{};
        buildMeta();
        spdlog::info("build blockbitmap");
        buildBitmap();
        m_meta.rootInum = m_inodeBitmap.alloc(1);
        initDirInode(m_meta.rootInum);
        return;
    }
    spdlog::info("build blockbitmap");
    buildBitmap();
}

void FSbasic::mapFileIntoMemory(std::string const& filename) {
    spdlog::info(__func__);
    m_fd = ::open(filename.data(), O_RDWR);
    if (m_fd < 0) {
        spdlog::error("open dumpfile fail, in file={}, line={}, func={}\n", __FILE__, __LINE__, __func__);
        exit(1);
    }
    struct stat sb;
    fstat(m_fd, &sb);
    m_size = sb.st_size;
    spdlog::info("dumpfile size = {} bytes", m_size);
    m_baseptr = mmap(nullptr, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
    if (m_baseptr == MAP_FAILED) {
        spdlog::error("map dumpfile fail, in file={}, line={}, func={}\n", __FILE__, __LINE__, __func__);
        exit(1);
    }
    spdlog::info("addr={},size = {}", m_baseptr, m_size);
}
void FSbasic::buildMeta() {
    //计算区域大小信息
    // meta | ibitmap | bbitmap | inodespace | blockspace |
    spdlog::info("cal info");
    size_t blockbitmapSize = ::std::ceil(1.0 * m_meta.blockBlockNum / (sizeof(Block) * 8));
    size_t blockspaceSize  = m_meta.blockBlockNum * m_meta.blockSize;
    size_t inodebitmapSize = std::ceil(1.0 * m_meta.inodeBlockNum / (sizeof(Block) * 8));
    size_t inodespaceSize  = m_meta.inodeBlockNum * m_meta.inodeSize;

    size_t totalSize = sizeof(Meta) + blockbitmapSize + blockspaceSize + inodebitmapSize + inodespaceSize;

    if (totalSize > m_size) {
        spdlog::info("dumpfile is too small, you need at least {} bytes, in file={}, line={}, func={}", totalSize,
                     __FILE__, __LINE__, __func__);
        exit(1);
    }

    //设置元信息
    m_meta.inodeBitmapOffset = sizeof(Meta);
    m_meta.blockBitmapOffset = sizeof(Meta) + inodebitmapSize;
    m_meta.inodeSpaceOffset  = sizeof(Meta) + inodebitmapSize + blockbitmapSize;
    m_meta.blockSpaceOffset  = sizeof(Meta) + inodebitmapSize + blockbitmapSize + inodespaceSize;
}

void FSbasic::buildBitmap() {
    char *pc = (char*)(m_baseptr), *mapoff, *spaceoff;
    mapoff   = pc + m_meta.blockBitmapOffset;
    spaceoff = pc + m_meta.blockSpaceOffset;
    m_blockBitmap.init(mapoff, spaceoff, m_meta.blockBlockNum, m_meta.blockSize);
    mapoff   = pc + m_meta.inodeBitmapOffset;
    spaceoff = pc + m_meta.inodeSpaceOffset;
    m_inodeBitmap.init(mapoff, spaceoff, m_meta.inodeBlockNum, m_meta.inodeSize);
}
size_t FSbasic::getRootInum()const { return m_meta.rootInum; }
Inode& FSbasic::toInode(size_t inum) { return *((Inode*)m_inodeBitmap.convertToAddr(inum)); }

void FSbasic::readdir(size_t inum, std::vector<DirEntry>& dir) {
    Inode& inode = toInode(inum);
    // debug("func={} msg={}", __func__, "get inode");
    Buf    blkbuf{inode, getBmap()};
    size_t nbyte = blkbuf.size();
    // debug("func={} nbyte={}", __func__, nbyte);
    int n = nbyte / sizeof(DirEntry);
    // debug("func={} dir num={}", __func__, n);
    DirEntry de;
    size_t   off = 0;
    while (n--) {
        blkbuf.read((char*)(&de), sizeof(DirEntry), off);
        off += sizeof(DirEntry);
        debug("func={} dir name={}, dir inum={}", __func__, de.name, de.inum);
        dir.push_back(de);
    }
}
void FSbasic::freeInode(size_t inum) {
    if (inum == -1) return;
    Inode& inode = toInode(inum);
    inode.nlink--;
    if (inode.nlink > 0) return;
    size_t nbyte = inode.size;
    size_t n     = nbyte / sizeof(Block);
    if (nbyte % sizeof(Block)) ++n;

    for (int i = 0; i < n; ++i) { getBmap()->dealloc(inode.addr[i], 1); }
    getImap()->dealloc(inum, 1);
}
size_t FSbasic::toInum(std::string const& path) {
    debug("func={} path={}", __func__, path);
    if (path == "/" || path == "") {
        debug("func={} msg={}", __func__, "is root dir");
        return m_meta.rootInum;
    }
    size_t inum = m_meta.rootInum;
    // debug("func={} root inum={}", __func__, inum);
    std::vector<std::string> res;
    parserPath(path, res);
    for (auto& filename : res) {
        // debug("func={} dirlookup file={}", __func__, filename);
        inum = dirLookup(inum, filename);
        if (inum == -1) {
            debug("func={} msg={}", __func__, "no found");
            // debug("func={} ret={}", __func__, -1);
            return -1;
        }
    }
    debug("func={} ret={}", __func__, inum);
    return inum;
}
size_t FSbasic::dirLookup(size_t inum, std::string const& filename) {
    if (inum == -1) return -1;
    std::vector<DirEntry> dir;
    debug("func={} try to lookup {} in  inum={}", __func__, filename, inum);
    readdir(inum, dir);
    for (auto& [name, ret_inum] : dir) {
        // debug("func={} dirname={}, dirinum={}", __func__, name,
        // ret_inum);
        if (std::string(name) == filename) { return ret_inum; }
    }
    debug("func={} msg={}", __func__, "no found");
    // debug("func={} ret={}", __func__, -1);
    return -1;
}
void FSbasic::parserPath(std::string const& path, std::vector<std::string>& res) {
    // debug("func={} path={}", __func__, path);
    std::stringstream ss(path);
    //"/as/n/d";
    std::string filename;
    while (std::getline(ss, filename, '/')) {
        if (filename.empty()) continue;
        // debug("func={} filename={}", __func__, filename);
        res.push_back(filename);
    }
}

void FSbasic::initDirInode(size_t inum) {
    debug("func={} inum={}", __func__, inum);
    Inode& inode = toInode(inum);
    inode.uid    = getuid();
    inode.gid    = getgid();
    inode.mode   = (S_IFDIR | 0755);
    inode.nlink  = 2;
    inode.size   = 0;
    clock_gettime(CLOCK_REALTIME, &(inode.ctim));
    clock_gettime(CLOCK_REALTIME, &(inode.atim));
    clock_gettime(CLOCK_REALTIME, &(inode.mtim));
}
void FSbasic::initFileInode(size_t inum) {
    debug("func={} inum={}", __func__, inum);
    Inode& inode = toInode(inum);
    // debug("func={} msg={}", __func__, "get inode");
    inode.uid   = getuid();
    inode.gid   = getgid();
    inode.mode  = S_IFREG | 0777;
    inode.nlink = 1;
    inode.size  = 0;
    clock_gettime(CLOCK_REALTIME, &(inode.ctim));
    clock_gettime(CLOCK_REALTIME, &(inode.atim));
    clock_gettime(CLOCK_REALTIME, &(inode.mtim));
    // inode.addr[0] = m_pBlockBitmap.alloc(1);
    // debug("func={} msg={}", __func__, "complete");
}
void FSbasic::removeDirEntry(size_t parentInum, size_t inum) {
    std::vector<DirEntry> dir;
    //读出目录
    debug("func={} readdir", __func__);
    readdir(parentInum, dir);

    //删除目录
    debug("func={} erase dir", __func__);
    for (auto beg = dir.begin(); beg != dir.end(); beg++) {
        debug("func={}, beg->inum={}", beg->inum);
        if (beg->inum == inum) {
            beg = dir.erase(beg);  // erase之后记着重新赋值,有迭代器失效的问题
            break;
        }
    }

    //写回目录
    debug("func={} write back dir", __func__);
    Buf    blkbuf{toInode(parentInum), getBmap()};
    size_t off = 0;
    for (auto& de : dir) {
        blkbuf.write((char*)(&de), sizeof(DirEntry), off);
        off += sizeof(DirEntry);
    }
    debug("func={} update parentDir's inode size", __func__);
    toInode(parentInum).size = dir.size() * sizeof(DirEntry);
}
void FSbasic::addDirEntry(size_t pinum, DirEntry const& de) {
    Buf blkbuf{toInode(pinum), getBmap()};
    blkbuf.write((char const*)(&de), sizeof(DirEntry), blkbuf.size());
}
Bitmap* FSbasic::getBmap() { return &m_blockBitmap; }
Bitmap* FSbasic::getImap() { return &m_inodeBitmap; }
}  // namespace basic
}  // namespace myFs